// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/dydx_futures/order_v3/parser.h"

#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include <nlohmann/json.hpp>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::dydx_v3::order_v3 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::SubmitOrderResponse;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline coin::proto::OrderSide GetOrderSide(const JsonValue& data) {
  using coin::proto::OrderSide;
  std::string_view direction = data["side"].GetString();
  if (direction == "BUY") {
    return OrderSide::BUY_ORDER;
  } else if (direction == "SELL") {
    return OrderSide::SELL_ORDER;
  } else {
    return OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("clientId")) {
    std::string_view proc_str = data["clientId"].GetString();
    try {
      proc_order_id = stoll(proc_str);
    } catch (std::exception& e) {
      LOG(ERROR) << e.what() << JsonToString(data);
    }
  }
  return proc_order_id;
}

bool IsDydxErrMessage(const JsonValue& doc) {
  if (doc.HasMember("errors") || doc.HasMember("error")) {
    return true;
  }
  return false;
}

}  // namespace

void DydxFuturesV3Parser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsDydxErrMessage(document)) {
    LOG(ERROR) << "[ParseAccountBalance] error response: " << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!document.HasMember("accounts") || !document["accounts"].IsArray()) {
    LOG(ERROR) << "[ParseAccountBalance] error response: " << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document["accounts"][0].HasMember("positionId")) {
    auto dydx_position_id = std::stoll(document["accounts"][0]["positionId"].GetString());
    mb_->PublishEvent("dydx_futures/position_id", reinterpret_cast<void*>(dydx_position_id));
    LOG_FIRST_N(INFO, 10) << "found position_id: " << dydx_position_id;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

AccountBalance DydxFuturesV3Parser::ParseBalance(const JsonValue& doc) {
  AccountBalance acc_balance;
  acc_balance.set_exchange("Dydx");
  acc_balance.set_market_type("Futures");

  auto* balance = acc_balance.add_each_balance();

  double total = stod(doc["accounts"][0]["equity"].GetString());
  double avail = stod(doc["accounts"][0]["freeCollateral"].GetString());
  double hold = total - avail;
  balance->set_currency("USD");
  balance->set_currency_native("USD");
  balance->set_total(total);
  balance->set_hold(hold);
  balance->set_available(avail);
  is_account_balance_initialized_ = true;
  return acc_balance;
}

void DydxFuturesV3Parser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (doc.IsObject() && IsDydxErrMessage(doc)) {
    LOG(ERROR) << "[ParseAccountPosition] error response: " << JsonToString(doc);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!doc.HasMember("accounts") || !doc["accounts"].IsArray()) {
    LOG(ERROR) << "[ParseAccountBalance] error response: " << JsonToString(doc);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_position = ParsePosition(doc);
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition DydxFuturesV3Parser::ParsePosition(const JsonValue& doc) {
  AccountPosition acc_position;
  acc_position.set_exchange("Dydx");
  acc_position.set_market_type("Futures");
  auto& pos = doc["accounts"][0]["openPositions"];

  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (pos.HasMember(native_symbol.c_str())) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (const auto& kv : pos.GetObject()) {
    auto* position = acc_position.add_each_position();
    position->set_native_symbol(kv.name.GetString());

    const auto& p = kv.value;
    if (std::string_view(p["side"].GetString()) == "LONG") {
      position->set_long_position(stod(p["size"].GetString()));
      position->set_net_position(position->long_position());
      position->set_available_long_position(position->long_position());
    } else if (std::string_view(p["side"].GetString()) == "SHORT") {
      position->set_short_position(std::abs(stod(p["size"].GetString())));
      position->set_net_position(-position->short_position());
      // TODO available_short_position should be positive like short_position?
      // position->short_position() rather than -position->short_position()
      position->set_available_short_position(-position->short_position());
    }
    DLOG(INFO) << "POS: " << kv.name.GetString() << " " << position->net_position();
  }

  is_account_position_initialized_ = true;
  return acc_position;
}

void DydxFuturesV3Parser::ParseWsPosition(const JsonDocument& doc) {
  auto& acc_position = *result_.mutable_account_position();
  acc_position.Clear();

  const auto& contents = doc["contents"];

  if (!contents.HasMember("positions") || !contents["positions"].IsArray()) {
    DLOG(ERROR) << "[ParseWsPosition] " << JsonToString(doc);
    return;
  }

  for (const auto& pos : contents["positions"].GetArray()) {
    auto native_symbol = pos["market"].GetString();

    auto* position = acc_position.add_each_position();
    position->set_native_symbol(native_symbol);

    if (std::string_view(pos["side"].GetString()) == "LONG") {
      position->set_long_position(stod(pos["size"].GetString()));
      position->set_net_position(position->long_position());
      position->set_available_long_position(position->long_position());
    } else if (std::string_view(pos["side"].GetString()) == "SHORT") {
      // short position is negative in exchange msg
      position->set_short_position(std::abs(stod(pos["size"].GetString())));
      position->set_net_position(-position->short_position());
      // TODO available_short_position should be positive like short_position?
      // position->short_position() rather than -position->short_position()
      position->set_available_short_position(-position->short_position());
    } else {
      CHECK(false) << "unknown position side" << JsonToString(doc);
    }
    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  if (acc_position.each_position_size() > 0) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
  }
}

void DydxFuturesV3Parser::ParseOrderInfo(
    const JsonValue& data,
    coin::proto::ProductOrderElement* order) {
  order->set_market_type(MarketType::Futures);
  order->set_exchange_type(ExchangeType::Dydx);
  order->set_api_version("v3");
  order->set_exchange_order_id(data["id"].GetString());
  order->set_symbol(data["market"].GetString());

  order->set_price(stod(data["price"].GetString()));
  order->set_qty(stod(data["size"].GetString()));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data));
  std::string_view type_str = data["type"].GetString();
  if (type_str == "MARKET") {
    order->set_order_type(coin::proto::OrderType::MARKET_ORDER);
  } else {
    order->set_order_type(coin::proto::OrderType::LIMIT_ORDER);
  }
  order->set_timestamp(msg_->timestamp);
  const std::string state = data["status"].GetString();
  if (state == "OPEN" || state == "PARTIALLY_FILLED") {
    order->set_is_live(true);
  } else if (state == "CANCELED") {
    order->set_cancel_confirmed(true);
  } else if (state == "FILLED") {
    order->set_fully_filled(true);
  } else if (state == "PENDING") {
    DLOG(INFO) << "order pending! " << state;
  } else {
    LOG(ERROR) << "Unknown order status! " << state;
  }
}

void DydxFuturesV3Parser::ParseRestOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();

  if (document.IsObject() && IsDydxErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOrders] " << JsonToString(document);
    return;
  }
  if (!document.HasMember("orders") || !document["orders"].IsArray()) {
    LOG(ERROR) << "[ParseRestOrders] " << JsonToString(document);
    return;
  }

  bool product_order_bundle_found = false;
  for (const auto& info : document["orders"].GetArray()) {
    auto native_symbol = info["market"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(info, order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void DydxFuturesV3Parser::ParseWsOrderInfoList(const JsonDocument& document) {
  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();

  const auto& contents = document["contents"];

  if (!contents.HasMember("orders") || !contents["orders"].IsArray()) {
    DLOG(ERROR) << "[ParseWsOrderInfoList] " << JsonToString(document);
    return;
  }

  bool product_order_bundle_found = false;
  for (const auto& info : contents["orders"].GetArray()) {
    auto native_symbol = info["market"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(info, order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void DydxFuturesV3Parser::ParseFillInfo(
    const JsonValue& data,
    coin::proto::ProductFillElement* fill) {
  fill->set_symbol(data["market"].GetString());
  fill->set_exchange_order_id(data["orderId"].GetString());
  fill->set_price(stod(data["price"].GetString()));
  fill->set_qty(stod(data["size"].GetString()));
  fill->set_fill_id(data["id"].GetString());
  fill->set_fee(stod(data["fee"].GetString()));
  fill->set_fee_currency("USDC");

  if (std::string_view(data["liquidity"].GetString()) == "TAKER") {
    fill->set_fill_type(FillType::TAKER_FILL_TYPE);
  } else if (std::string_view(data["liquidity"].GetString()) == "MAKER") {
    fill->set_fill_type(FillType::MAKER_FILL_TYPE);
  } else {
    fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
  }
}

void DydxFuturesV3Parser::ParseRestFillOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();

  if (document.IsObject() && IsDydxErrMessage(document)) {
    LOG(ERROR) << "[ParseRestFillOrders] " << JsonToString(document);
    return;
  }

  if (!document.HasMember("fills") || !document["fills"].IsArray()) {
    LOG(ERROR) << "[ParseRestFillOrders] " << JsonToString(document);
    return;
  }

  bool product_fill_bundle_found = false;
  for (const auto& info : document["fills"].GetArray()) {
    const std::string native_symbol = info["market"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    const std::string fill_type = info["type"].GetString();
    if (fill_type != "LIMIT" && fill_type != "MARKET") {
      continue;  // skip handle LIQUIDATED order
    }
    auto* fill = product_fill_bundle->add_each_fill();
    ParseFillInfo(info, fill);
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void DydxFuturesV3Parser::ParseWsFillInfoList(const JsonDocument& document) {
  // same as ParseWsOrderInfoList
  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();

  const auto& contents = document["contents"];

  if (!contents.HasMember("fills") || !contents["fills"].IsArray()) {
    DLOG(ERROR) << "[ParseWsFillInfoList] " << JsonToString(document);
    return;
  }

  bool product_fill_bundle_found = false;
  for (const auto& info : contents["fills"].GetArray()) {
    const std::string native_symbol = info["market"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    const std::string fill_type = info["type"].GetString();
    if (fill_type != "LIMIT" && fill_type != "MARKET") {
      continue;  // skip handle LIQUIDATED order
    }

    auto fill = product_fill_bundle->add_each_fill();
    ParseFillInfo(info, fill);
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void DydxFuturesV3Parser::ParseRateLimit() {
  /*
    "RateLimit-Remaining": "0",
    "RateLimit-Reset": "1636540973815",
    "Retry-After": "7485",
    "RateLimit-Limit": "1000",
  */
  const auto& http_context = msg_->http_context;
  const auto& header = http_context->res.base();
  nlohmann::json json = nlohmann::json::object();

  int found = 0;
  json["method"] = http_context->req.method_string();
  if (json["method"] != "GET") {
    json["native_symbol"] = native_symbol_;
  }
  if (header.count("RateLimit-Remaining") > 0) {
    json["ratelimit-remaining"] = std::stoll(header.at("RateLimit-Remaining").data());
    found++;
  }
  if (header.count("RateLimit-Reset") > 0) {
    json["ratelimit-reset"] = std::stoll(header.at("RateLimit-Reset").data());
    found++;
  }
  if (header.count("RateLimit-Limit") > 0) {
    json["ratelimit-limit"] = std::stoll(header.at("RateLimit-Limit").data());
    found++;
  }

  if (found == 3) {
    std::string s = json.dump();
    mb_->PublishEvent("dydx_futures/rate_limit", reinterpret_cast<void*>(&s));
  }
}

void DydxFuturesV3Parser::ParseSubmitResponse() {
  /*
  {
    "order": {
      "id": "385d9ab8aaed61f229e0eb43ba855763414fe75414adc78effe73f346d837d8",
      "clientId": "47015928418163456",
      "accountId": "34578d73-f0b6-5f00-9fa1-f44843249775",
      "market": "BTC-USD",
      "side": "SELL",
      "price": "61600",
      "triggerPrice": null,
      "trailingPercent": null,
      "size": "0.0013",
      "remainingSize": "0.0013",
      "type": "LIMIT",
      "createdAt": "2021-11-01T00:19:41.760Z",
      "unfillableAt": null,
      "expiresAt": "2021-11-01T00:21:41.632Z",
      "status": "PENDING",
      "timeInForce": "GTT",
      "postOnly": false,
      "cancelReason": null
    }
  }
  {
    "errors": [
      {
        "msg": "[big.js] Invalid number",
        "param": "limitFee",
        "location": "body"
      },
      {
        "msg": "expiration must be an ISO8601 string",
        "param": "expiration",
        "location": "body"
      }
    ]
  }
  */

  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  native_symbol_ = order_context->order_spec().product;

  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[DydxSubmitOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[DydxSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_exchange_error_msg(JsonToString(document));
    resp->set_success(false);
    return;
  }

  if (!document.HasMember("order")) {
    LOG(ERROR) << "[DydxSubmitOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_exchange_error_msg(JsonToString(document));
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_exchange_order_id(document["order"]["id"].GetString());
}

void DydxFuturesV3Parser::ParseCancelResponse() {
  /*
  {
    "cancelOrder": {
      "id": "21b884fe57df8fd7dd1ffa66fe5c74663f95d4a0b32c9749bc7a22501a37fd3",
      "clientId": "12285248154183292",
      "accountId": "34578d73-f0b6-5f00-9fa1-f44843249775",
      "market": "BTC-USD",
      "side": "SELL",
      "price": "62600",
      "triggerPrice": null,
      "trailingPercent": null,
      "size": "0.0013",
      "remainingSize": "0.0013",
      "type": "LIMIT",
      "createdAt": "2021-11-02T00:29:59.611Z",
      "unfillableAt": null,
      "expiresAt": "2021-11-02T00:39:58.332Z",
      "status": "OPEN",
      "timeInForce": "GTT",
      "postOnly": true,
      "cancelReason": null
    }
  }
  DydxApiError(status_code=400, response={'errors': [{'msg': 'Cannot cancel order with id:
  21b884fe57df8fd7dd1ffa66fe5c74663f95d4a0b32c9749bc7a22501a37fd3 because it is already
  canceled'}]}) DydxApiError(status_code=400, response={'errors': [{'msg': 'No order exists with id:
  21b884fe57df8fd7dd1ffa66fe5c74663f95d4a0b32c9749bc7a22501a37fd3'}]}) body:
  "{\"errors\":[{\"msg\":\"Cannot cancel order with id:
  2fd857044bef423c9e367a540f7616c4b4b0958a129749f1c6f46f677c8041b because it is already
  canceled\"}]}"
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  native_symbol_ = order_context->order_spec().product;

  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[DydxCancelOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[DydxCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_exchange_error_msg(JsonToString(document));
    resp->set_success(false);
    return;
  }

  if (document.HasMember("errors")) {
    std::string_view msg(document["errors"][0]["msg"].GetString());
    if (msg.find("it is already canceled") != std::string::npos) {
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_CANCELED_ALREADY);
    } else if (msg.find("No order exists with id") != std::string::npos) {
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NOT_FOUND);
    }
    LOG(ERROR) << "[DydxCancelOrder] cancel done order" << msg_->DebugString() << "\n"
               << resp->DebugString();
    return;
  }

  if (!document.HasMember("cancelOrder")) {
    LOG(ERROR) << "[DydxCancelOrder] Error response." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  coin::proto::OrderErrorCode error_code = coin::proto::OrderErrorCode::ORDER_NO_ERROR;
  if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "Cancel failed! " << JsonToString(document) << ", "
               << order_context->exchange_order_id();
    resp->set_success(false);
    return;
  }

  resp->set_exchange_order_id(document["cancelOrder"]["id"].GetString());
  resp->set_success(true);
}

void DydxFuturesV3Parser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  DLOG(INFO) << "OG ws msg: " << JsonToString(document);

  if (!document.HasMember("contents") || !document["contents"].IsObject()) {
    LOG(ERROR) << "[ParseWsMessage] " << JsonToString(document);
    return;
  }

  ParseWsFillInfoList(document);
  ParseWsOrderInfoList(document);
  ParseWsPosition(document);
}

auto DydxFuturesV3Parser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      // pass
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOrders();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillOrders();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      ParseRestAccountPosition();
      break;
    default:
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      break;
  }
  ParseRateLimit();
  return result_;
}

}  // namespace coin2::exchange::dydx_v3::order_v3
